<?php

require_once('broker/BrokerException.php');
require_once('Constants.php');
require_once('IOException.php');

//error_reporting(E_ERROR);
require_once('simpletest/browser.php');
//error_reporting(E_ALL | E_STRICT);

class StockP_broker_SogoBroker extends StockP_broker_Broker {
	/**
	 * A flat per-transaction commission that Sogo charges.
	 */
	
	const FLAT_COMMISSION = 3.00;
	
	/**
	 * An HTTP browser/client used to fetch webpages.
	 */

	protected $browser = null;
	
	/**
	 * True if the user is logged in, false otherwise.
	 */
	
	protected $loggedIn = false;
	
	/**
	 * Constructor.
	 * @throws StockP_BrokerException if there's a problem creating the browser.
	 */

	public function __construct() {
		$this->browser = new SimpleBrowser();

		if ($this->browser === false) {
			throw new StockP_BrokerException('Cannot create browser object');
		}
	}
	
	/**
	 * Calcuate the commision fee of a transaction.
	 * @param int $type transaction type
	 */
	
	public function getCommissionFee($type, $mode, $unitPrice, $shareCount) {
		return FLAT_COMMISSION;
	}
	
	/**
	 * Checks if the "user" is logged in.
	 * @return True if the user is logged in, false if not.
	 */

	public function isLoggedIn() {
		return $this->loggedIn;
	}
	
	/**
	 * Log into the website.
	 * @throws StockP_BrokerException if the website gives unexpected content.
	 * @throws StockP_IOException if there's a network leve error.
	 */

	public function login() {
		$this->loggedIn = false;

		$browser = $this->browser;

		$browser->get('https://www.sogoinvest.com/Account/Login.aspx');
		$browser->setField('ctl00$ContentPlaceHolder1$Ctluserpass2$txtboxUserName', $this->username);
		$browser->setField('ctl00$ContentPlaceHolder1$Ctluserpass2$txtboxPassword', $this->password);
		$page = $browser->clickSubmitByName('ctl00$ContentPlaceHolder1$Ctluserpass2$btnLogon');

		if ($page === false) {
			throw new StockP_IOException($browser->getTransportError());
		}
		if (strpos($page, 'Account Overview') === false) {
			throw new StockP_BrokerException('"Account Overview" page is not received properly');
		}

		$this->loggedIn = true;
	}
	
	/**
	 * "Ping" or poll the website to make sure that the session is active.
	 * @param bool $autoLogin if true, attempt to log in again if the session has ended.
	 * @return bool true if the session is active, false if it has ended and not restarted.
	 */

	public function ping($autoLogin = true) {
		$page = $this->browser->get('https://www.sogoinvest.com/Account/Default.aspx');
		if ($page === false) {
			throw new StockP_IOException($browser->getTransportError());
		}
		
		// If the browser got redirected to the login page,
		// We assume the session has timed out
		
		if (strpos($page, 'Account Login') !== false) {
			
			
			if ($autoLogin) {
				$this->login();
				return $this->ping(false);
			}
			else {
				return false;
			}
		}
		
		// If 
		
		else if (strpos($page, 'Account Overview') === false) {
			throw new StockP_BrokerException('"Account Overview" page is not received properly');
		}
	}
	
	/**
	 * Get a quote without preferring delayed or real-time quote.
	 * @param string $symbol the stock's symbol.
	 * @return array an array in this format: (<last>, <bid>, <ask>). Not all elements are required.
	 */

	public function quote($symbol) {
		if ($this->loggedIn) {
			return $this->quoteRealTime($symbol);
		}
		else {
			return $this->quoteDelayed($symbol);
		}
	}
	
	/**
	 * Get a delayed quote.
	 * @param string $symbol the stock's symbol.
	 * @return array an array in this format: (<last>, <bid>, <ask>). Not all elements are required.
	 */

	public function quoteDelayed($symbol) {
		// Symbol must not be empty

		if (empty($symbol)) {
			throw new StockP_IllegalArgumentException('\$symbol must not be empty');
		}
		
		// Go to "Quotes & Charts" page to get the prices

		$page = $this->browser->get('http://custom.marketwatch.com/custom/genesis/chartquotesnews/htmlinc-quoteintchart.asp?symb=' . urlencode($symbol));
		if ($page === false) {
			throw new StockP_IOException($browser->getTransportError());
		}
		if (!preg_match('|<span class=mwquotedatabold><NOBR>(.*?)</NOBR></span>|', $page, $matches)) {
			throw new StockP_BrokerException('"Real-time Quote" page is not received properly');
		}

		return array($matches[1]);
	}
	
	/**
	 * Get a real-time quote.
	 * @param string $symbol the stock's symbol.
	 * @return array an array in this format: (<last>, <bid>, <ask>). Not all elements are required.
	 */

	public function quoteRealTime($symbol) {
		// Symbol must not be empty

		if (empty($symbol)) {
			throw new StockP_IllegalArgumentException('\$symbol must not be empty');
		}

		// Go to "Real-time Quote" page to get the prices

		$page = $this->browser->get('https://www.sogoinvest.com/Trade/SymbolSearch.aspx?ShortSymbol=' . urlencode($symbol));
		if ($page === false) {
			throw new StockP_IOException($browser->getTransportError());
		}
		if (!preg_match('/Bid: (.*?)&nbsp; Ask: (.*?)&nbsp; Last: (.*?)&nbsp;/', $page, $matches)) {
			throw new StockP_BrokerException('"Real-time Quote" page is not received properly');
		}

		// Extract bid & ask prices

		list($none, $bid, $ask, $last) = $matches;
		return array($last, $bid, $ask);
	}
	
	/**
	 * Submit a transaction (buy/sell action) to the website.
	 * @param bool $type transaction type, can be buy (StockP_Constants::TYPE_BUY) or sell (StockP_Constants::TYPE_SELL).
	 * @param bool $mode transaction mode, can be limit (StockP_Constants::MODE_LIMIT) or market (StockP_Constants::MODE_MARKET).
	 * @param string $symbol the symbol of the stock to buy/sell.
	 * @param float $unitPrice price per share of stock.
	 * @param float $shareCount number of shares to trade.
	 */

	public function transaction($type, $mode, $symbol, $unitPrice, $shareCount) {
		// If not logged in, do so

		if (!$this->loggedIn) {
			$this->login();
		}

		// Type must be either TYPE_BUY or TYPE_SELL

		switch ($type) {
			case StockP_Constants::TYPE_BUY:
				$grpSellBuy = 'rbtnlistBuy';
				break;
			case StockP_Constants::TYPE_SELL:
				$grpSellBuy = 'rbtnlistSell';
				break;
			default:
				throw new StockP_IllegalArgumentException('\$type must be either TYPE_BUY or TYPE_SELL');
		}

		// Mode must be either MODE_LIMIT or MODE_MARKET

		switch ($mode) {
			case StockP_Constants::MODE_LIMIT:
				$grpOrderType = 'radioLimit';
				break;
			case StockP_Constants::MODE_MARKET:
				$grpOrderType = 'radioMarket';
				break;
			default:
				throw new StockP_IllegalArgumentException('\$mode must be either MODE_LIMIT or MODE_MARKET');
		}

		// Symbol must not be empty

		if (empty($symbol)) {
			throw new StockP_IllegalArgumentException('\$symbol must not be empty');
		}

		// Unit price & share count must be positive integers

		if (!($unitPrice > 0)) {
			throw new StockP_IllegalArgumentException('\$symbol must be an integer > 0');
		}

		if (!($shareCount > 0)) {
			throw new StockP_IllegalArgumentException('\$symbol must be an integer > 0');
		}

		// Data is valid at this point

		$browser = $this->browser;

		// Go to "Real-time Trade" page (step 1)

		$page = $browser->get('https://www.sogoinvest.com/Trade/Realtime.aspx');
		if ($page === false) {
			throw new StockP_IOException($browser->getTransportError());
		}
		if (strpos($page, 'Real-time Trade') === false) {
			throw new StockP_BrokerException('"Real-time Trade" page is not received properly');
		}

		// From "Real-time Trade" page (step 1),
		// go to "Real-time Trades Verify" page (step 2)

		$browser->setField('ctl00$ContentPlaceHolder1$grpSellBuy', $grpSellBuy);
		$browser->setField('ctl00$ContentPlaceHolder1$txtboxSymbol3', $symbol);
		$browser->setField('ctl00$ContentPlaceHolder1$txtboxShares', $shareCount);
		$browser->setField('ctl00$ContentPlaceHolder1$grpOrderType', $grpOrderType);
		$browser->setField('ctl00$ContentPlaceHolder1$txtboxLimitPrice', $unitPrice);
		$page = $browser->clickSubmitByName('ctl00$ContentPlaceHolder1$btnNext');
		if ($page === false) {
			throw new StockP_IOException($browser->getTransportError());
		}
		if (strpos($page, 'Real-time Trades Verify') === false) {
			throw new StockP_BrokerException('"Real-time Trades Verify" page is not received properly');
		}

		// From "Real-time Trades Verify" page (step 2), submit order and
		// go to "Real-time Trades Confirmation" page (step 3)

		$page = $browser->clickSubmitByName('ctl00$ContentPlaceHolder1$btnPlaceOrder');
		if ($page === false) {
			throw new StockP_IOException($browser->getTransportError());
		}
		if (strpos($page, 'Your order has been placed') === false) {
			throw new StockP_BrokerException('"Real-time Trades Confirmation" page is not received properly');
		}
	}
}

?>